/*
 * Copyright (C) 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * This code is not supported by Google
 *
 */

/*parts of this code is taken from
 * http://thejavamonkey.blogspot.com/2008/04/clientserver-hello-world-in-kerberos.html
 * http://krbdev.mit.edu/rt/Ticket/Display.html?user=guest&pass=guest&id=6402
 * http://www.ietf.org/rfc/rfc4178.txt
 * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6815182
 * 
 * 
 * see:
 * http://code.google.com/p/gsa-kerberos-test-client/wiki/GSAKerbeorsTestApp
 * 
 * Requires SUN Java 1.7+
 * javac -classpath .:commons-codec-1.4.jar:commons-logging-1.1.1.jar:
 * 					httpclient-4.1.1.jar:httpclient-cache-4.1.1.jar:
 * 					httpcore-4.1.jar:httpmime-4.1.1.jar *.java
 * 
 * java -classpath .:commons-codec-1.4.jar:commons-logging-1.1.1.jar:
 * 					httpclient-4.1.1.jar:httpclient-cache-4.1.1.jar:
 * 					httpcore-4.1.jar:httpmime-4.1.1.jar DecodeToken 
 */
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Properties;
import java.util.zip.GZIPInputStream;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.Oid;

public class DecodeToken {
	
	  private static Oid krb5Oid;	
	  private static Oid spnegoMechOid; 
	  private Subject subject;	  
	  private String targetSPN;
	  boolean kerberos_crawl = false;
	  private boolean debug_flag = false;
 
  public static void main( String[] args) {
	  DecodeToken ss = new DecodeToken(args);
  }
  
  public DecodeToken (String[] args)
  {
      
	    try {
	    	System.out.println("Starting Decoder...");
	    	
	        Properties props = new Properties();
	        props.load( new FileInputStream( "system.properties"));
	       
	        debug_flag = Boolean.getBoolean(props.getProperty("debug"));
	        
	        System.setProperty( "sun.security.krb5.debug",  Boolean.toString(debug_flag));
	        System.setProperty( "sun.security.spnego.debug", Boolean.toString(debug_flag));
	        System.setProperty("java.security.krb5.conf",props.getProperty("kerberos.conf.file"));
	        System.setProperty( "java.security.auth.login.config", "jaas.conf");
	        System.setProperty( "javax.security.auth.useSubjectCredsOnly", "false");
	        krb5Oid = new Oid( "1.2.840.113554.1.2.2");
	        spnegoMechOid = new Oid("1.3.6.1.5.5.2");

	        this.targetSPN= props.getProperty("decode.target.principal.name");

	        this.subject = login();	        	 	                
	        
	        byte serviceTicket[] = loadTokenFromDisk();	  
	        	        
	        System.out.println("========== START SERVICE TKT KEYINFO =============");
	        GSSContext client_context = this.getClientContext(serviceTicket);	
		    System.out.println("========== END SERVICE TKT KEYINFO ===============");

		    
		     System.out.println("========== START Acquire Delegated Creds  =============");
	          if (!client_context.getCredDelegState())
	        	  System.out.println("DELEGATION NOT ENABLED");
	          else
	          {
				     GSSCredential  gcred = client_context.getDelegCred(); 
				     
				     System.out.println("Attempting to get delegated credentials form Client " + client_context.getSrcName() + "  for SPN " + this.targetSPN);
				       
				     GSSManager dmanager = GSSManager.getInstance();
				     				     
				     GSSName gssServerName = dmanager.createName(this.targetSPN, GSSName.NT_USER_NAME);  
				     final GSSContext clientContext = dmanager.createContext(gssServerName.canonicalize(spnegoMechOid), spnegoMechOid, gcred, GSSContext.DEFAULT_LIFETIME);  
		
				     
				     byte[] delegated_serviceTicket = Subject.doAs( this.subject, new PrivilegedAction<byte[]>() {
				             public byte[] run() {
				                 try {
				                   byte[] token = new byte[0];

				                   clientContext.requestMutualAuth(true);
				                   clientContext.requestCredDeleg(true);   
					    		    clientContext.requestReplayDet(false);
					    		    clientContext.requestSequenceDet(false);
					    		    clientContext.requestConf(false);
					    		    clientContext.requestInteg(true); 
					    		    clientContext.requestAnonymity(false); 
					    		    
				                   byte[] bcontext = clientContext.initSecContext( token, 0, token.length);
				                   
				                   System.out.println("Acquired delegated service ticket FROM/TO ");
				                   System.out.println("FROM:---------> " + clientContext.getSrcName());
				                   System.out.println("TO:---------> " + clientContext.getTargName());
				                   System.out.println("Using Mechanism: " + clientContext.getMech());
				                   //System.out.println("delegation state:---------> " + clientContext.getCredDelegState() );
				                   byte[] encodedBytes  = org.apache.commons.codec.binary.Base64.encodeBase64(bcontext);
			                   
				                   String svc_tkt = new String(encodedBytes);
				                   System.out.println("Base64Encoded delegated service ticket (HTTP Authorization Header): " + svc_tkt);
				                   return bcontext;
				                 }
				                 catch ( GSSException e) {
				                   e.printStackTrace();
				                   return null;
				                 }
				               }
				             });
			         			     
				     System.out.println("========== END Acquire Delegated Creds  =============");
			         		
	          }        
	      }
	      catch ( LoginException e) {
	        e.printStackTrace();
	        System.err.println( "There was an error during the JAAS login");
	        System.exit( -1);
	      }
	      catch ( GSSException e) {
	        e.printStackTrace();
	        System.err.println( "There was an error during the security context acceptance");
	        System.exit( -1);
	      }
	      catch ( IOException e) {
	        e.printStackTrace();
	        System.err.println( "There was an IO error");
	        System.exit( -1);
	      }	  	  
  }
 
  // Load the security token from disk and decode it. Return the raw GSS token.
  private static byte[] loadTokenFromDisk() throws IOException {
    BufferedReader in = new BufferedReader( new FileReader( "security.token"));  
    String str;
    StringBuffer buffer = new StringBuffer();
    while ((str = in.readLine()) != null) {
       buffer.append( str + "\n");
    }
    in.close();
    return org.apache.commons.codec.binary.Base64.decodeBase64(buffer.toString().getBytes());
  }
  
  // Authenticate with the keytab using JAAS.
  private Subject login() throws LoginException {
    LoginContext loginCtx = null;
    // "Client" references the JAAS configuration in the jaas.conf file.
    System.out.println("========== START LOGIN =============");
    System.out.println("Attempting to use keytab..");
    loginCtx = new LoginContext( "DecodeToken", new LoginCallbackHandler(null,null));   
    loginCtx.login();
    Subject ret_sub = loginCtx.getSubject();
    System.out.println("Login Successful...  " );
    System.out.println("Keytab Key Version number: " );    
    System.out.println( ret_sub);
    System.out.println("If necessary, compare KeyVersion number here with AD attribute 'msDS-KeyVersionNumber' for this SPN" );
    
    //http://technet.microsoft.com/en-us/library/cc738207.aspx
    System.out.println("========== END LOGIN ===============");
    return ret_sub;
  }
 
  // Completes the security context initialisation and returns the client name.
  private GSSContext   getClientContext( final byte[] serviceTicket)
      throws GSSException {

    GSSContext rcontext=  Subject.doAs( subject, new PrivilegedAction<GSSContext>() {
      public GSSContext run() {
        try {
        	
          System.out.println("Attempting to decode service ticket...");
          GSSManager manager = GSSManager.getInstance();
          Oid[] mechs=manager.getMechs();
          System.out.println("Supported Mechs:> "+mechs+", "+Arrays.toString(mechs));

          GSSContext context = manager.createContext( (GSSCredential) null); 
          context.acceptSecContext( serviceTicket, 0, serviceTicket.length);         
           
	        System.out.println( "Security context successfully decoded");
	        System.out.println( "Mechanism " + context.getMech());
	        System.out.println( "Service Ticket originally sent from Client/Target: ");
	        System.out.println( "Client is " + context.getSrcName().toString() );
	        System.out.println( "Target is " + context.getTargName().toString() );
			return context;
        }
        catch ( Exception e) {
          e.printStackTrace();
          return null;
        }
      }
    });
    
    return rcontext;
  } 
}